%{
#include "egpd.h"

TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
static struct file {
	TAILQ_ENTRY(file) entry;
   FILE     *stream;
   char     *name;
   int      lineno;
   int      errors;
} *file, *topfile;

TAILQ_HEAD(listhead, list);
struct list {
   TAILQ_ENTRY(list) entry;
   union {
      char *string;
      u_int16_t number;
   } value;
};

typedef struct {
   union {
      struct ipaddr addr;
      int64_t number;
      char *string;
		u_int8_t u8;
      int type;
      int action;
   } v;
   int lineno;
} YYSTYPE;   

#if __APPLE__ || __MACH__
#define YYSTYPE_IS_DECLARED 1
#endif

struct listhead plist;

struct file *pushfile(const char *, int);
int popfile(void);
int yyparse(void);
int yylex(void);
int yyerror(const char *, ...);
int key_cmp(const void *, const void *);
int lookup(char *);
int lgetc(int);
int lungetc(int);
int findeol(void);
void addnumber2list(u_int16_t);
void addstring2list(const char *);
int plist_length(void);
%}

%token ASN DESC TYPE GROUP REMOTEAS LOCALAS PREFIXLIST POLICY TERM FROM
%token ROUTEFILTER THEN ASPATH INTERNAL EXTERNAL ACCEPT REJECT NEXT PRIORITY
%token PREPEND EXPORT POLICYIN POLICYOUT NEIGHBOR
%token <v.string> STRING
%token <v.number> NUMBER
%type <v.action> thenaction
%type <v.type> typeopts
%type <v.addr> prefix address
%type <v.number> asnumber
%type <v.string> string
%%

grammar : 
        | grammar '\n'
        | grammar asnumber '\n'
		  | grammar mainconfig '\n'
        | grammar error '\n' { file->errors++; }
        ;

optnl : '\n' optnl
      |
      ;

nl : '\n' optnl
   |
   ;

asnumber : NUMBER {
         if ($1 < 0 || $1 > USHRT_MAX) {
            yyerror("AS too big: max %u", USHRT_MAX);
            YYERROR;
         }
      }
		
string : string STRING {
       if (asprintf((char **)&$$, "%s %s", $1, $2) == -1)
         yyerror("string: asprintf");

       free($1);
       free($2);
   }
   | STRING
   ;

prefix : STRING '/' NUMBER {
       memset(&$$, 0, sizeof(struct ipaddr));
       if (string2addr($1, $3, &$$) != 1)
            yyerror("invalid prefix %s/%llu", $1, $3);
       }
       ;

address : STRING {
         memset(&$$, 0, sizeof(struct ipaddr));
         if (string2addr($1, 0, &$$) != 1)
            yyerror("invalid address %s", $1);
            
        };

mainconfig : ASN asnumber {
           conf->asnumber = $2;
           }
           | group
           | prefixlist
           | policy
           ;

policy : POLICY STRING optnl '{' optnl {
       struct policy *pol = new_policy();
       pol->name = strdup($2);
       LIST_INSERT_HEAD(&conf->policy, pol, entry);
       }
       policyoptions '}' {
       }
       ;

policyoptions : policyoptions policyopts
              | policyopts
              ;

policyopts : termdef nl
           | policydescr nl
           | error nl
           ;

policydescr : DESC string {
               get_firstpolicy()->description = strdup($2);
            }
            ;

termdef : TERM STRING optnl '{' optnl {
            struct policy *policy = get_firstpolicy();
            if (policy->termcount < MAXTERMS) {
               memset(&policy->terms[policy->termcount], 0, sizeof(
                  struct policy_term));
               policy->terms[policy->termcount].name = strdup($2);
            }
         }
         termoptions '}' {
            struct policy *pol = get_firstpolicy();
            int cnt = pol->termcount;
            int action = pol->terms[cnt].hasaction;
            if (action == 0) 
               yyerror("policy %s term %s does NOT have an action defined",
                  pol->name, pol->terms[cnt].name);
            else {
               if (pol->termcount < MAXTERMS)
                  pol->termcount++;
            }
         }
         ;

termoptions : termoptions termopts
            | termopts
            ;

termopts : fromdef nl
         | thendef nl
         | termdesc nl
         | error nl
         ;

fromdef :  FROM PREFIXLIST STRING {
            struct prefixlist *pfl = get_pflbyname($3);
            if (pfl == NULL)
               yyerror("prefixlist %s not defined", $3);
            else 
               insert_prefixlist_topolicy(pfl);
        }
        | FROM ROUTEFILTER prefix {
            insert_route_topolicy(&$3);
        }
        | FROM ROUTEFILTER address {
            insert_route_topolicy(&$3);
        }
        | FROM ASPATH NUMBER {
            if ($3 < 0 || $3 > MAXPREPEND)
               yyerror("as-path number is invalid %d", $3);
            else
               insert_aspath_topolicy($3);
        }
        ;

thenaction : ACCEPT { $$ = ACTION_ACCEPT; }
           | REJECT { $$ = ACTION_REJECT; }
           | NEXT { $$ = ACTION_NEXT; }
           ;

thendef : THEN thenaction {
            struct policy *pol = get_firstpolicy();
            int cnt = pol->termcount;
            pol->terms[cnt].then.action = $2;
            pol->terms[cnt].hasaction = 1;
         }
         | THEN PRIORITY NUMBER {
            struct policy *pol = get_firstpolicy();
            int cnt = pol->termcount;
            if ($3 > 65535) 
               yyerror("invalid priority %llu", $3);
            else
               pol->terms[cnt].then.priority = $3;
         }
         | THEN PREPEND aslist {
            int pcount = 0;
            struct list *ptr;
            struct policy *pol = get_firstpolicy();
            int cnt = pol->termcount;
            while ((ptr = TAILQ_FIRST(&plist))) {
               TAILQ_REMOVE(&plist, ptr, entry);
               if (pcount < MAXPREPEND)
                  pol->terms[cnt].then.prepend[pcount++] = ptr->value.number;
               else
                  yyerror("invalid number of aspath prepends");
                  
               free(ptr);
            }
         }
         ;

aslist : NUMBER {
         addnumber2list($1);
      }
      | NUMBER aslist {
         addnumber2list($1);
      }
      ;

termdesc : DESC string {
            struct policy *pol = get_firstpolicy();
            int cnt = pol->termcount;
            pol->terms[cnt].description = strdup($2);
         }
         ;

prefixlist : PREFIXLIST STRING optnl '{' optnl { 
           struct prefixlist *pflist = new_prefixlist();
           pflist->name = strdup($2);
           LIST_INSERT_HEAD(&conf->pfl, pflist, entry);
           }
           prefixoptions '}' {
           }
           ;

prefixoptions : prefixoptions prefixopts
              | prefixopts
              ;

prefixopts : networkdef nl
           | error nl
           ;

networkdef : prefix {
           if (addto_prefixlist(&$1)) 
               yyerror("cannot add more prefixes, max: %d", MAXPREFIX);
         }
         | address {
           if (addto_prefixlist(&$1)) 
               yyerror("cannot add more prefixes, max: %d", MAXPREFIX);
         }
         ;


group : GROUP STRING optnl '{' optnl {
         struct egpd_group *group = new_group();
         group->name = strdup($2);
         LIST_INSERT_HEAD(&conf->groups, group, entry);
         }
         groupoptions '}' {

         struct egpd_group *group = get_firstgroup();
         if (group->localas == 0)
            group->localas = conf->asnumber;
         }
         ;

groupoptions : groupoptions groupopts
             | groupopts
             ;

groupopts : groupdescr nl
          | grouptype nl
          | remoteas nl
          | localas nl
          | exportoptions nl
          | importoptions nl
          | neighbor nl
          | error nl
          ;

groupdescr : DESC string {
           get_firstgroup()->description = strdup($2);
           }
           ;

typeopts : INTERNAL { $$ = PEER_INT; }
         | EXTERNAL { $$ = PEER_EXT; }
         ;

grouptype : TYPE typeopts {
          get_firstgroup()->type = $2;
          }
          ;

remoteas : REMOTEAS asnumber {
         get_firstgroup()->remoteas = $2;
         }
         ;

localas : LOCALAS asnumber {
        get_firstgroup()->localas = $2;
        }
        ;

neighbor : NEIGHBOR address {
        insert_neighbor_togroup(&$2, 0);
        }
        | NEIGHBOR address '{' optnl {
         insert_neighbor_togroup(&$2, 1);
        } 
        neighboroptions '}' {
            struct egpd_group *group = get_firstgroup();
            struct egpd_peer *peer = &group->peers[group->peercnt];
            if (peer->asnumber == 0)
               peer->asnumber = group->remoteas;

            peer->route = NULL;
            group->peercnt++;
        }
        ;

neighboroptions : neighboroptions neighboropts
                | neighboropts
                ;

neighboropts : neighboras nl
             | neighbordescr nl
             | error nl
             ;

neighboras : REMOTEAS NUMBER {
               struct egpd_group *group = get_firstgroup();
               struct egpd_peer *peer = &group->peers[group->peercnt];
               peer->asnumber = $2;
            }
            ;

neighbordescr : DESC string {
               struct egpd_group *group = get_firstgroup();
               struct egpd_peer *peer = &group->peers[group->peercnt];
               peer->description = strdup($2);
            }
            ;

exportoptions : POLICYOUT policy_list {
              struct egpd_group *group = get_firstgroup();
              struct list *ptr;
              while ((ptr = TAILQ_FIRST(&plist))) {
                  TAILQ_REMOVE(&plist, ptr, entry);
                  struct policy *pol = get_policy(ptr->value.string);
                  if (pol == NULL) 
                     yyerror("%s is not defined", ptr->value.string);
                  else {
                     if (group->expcnt < MAXPOLICY) {
                        memcpy(&group->exp[group->expcnt], pol,
                           sizeof(struct policy));
                        group->expcnt++;
                     }
                     else
                        yyerror(
                           "export policy is greater than maximum allowed");
                  }

                  free(ptr->value.string);
                  free(ptr);
              }
              }
              ;

importoptions : POLICYIN policy_list {
               struct egpd_group *group = get_firstgroup();
               struct list *ptr;
               while ((ptr = TAILQ_FIRST(&plist))) {
                  TAILQ_REMOVE(&plist, ptr, entry);
                  struct policy *pol = get_policy(ptr->value.string);
                  if (pol == NULL)
                     yyerror("%s is not defined", ptr->value.string);
                  else {
                     if (group->impcnt < MAXPOLICY) {
                        memcpy(&group->exp[group->impcnt], pol,
                           sizeof(struct policy));
                        group->impcnt++;
                     }
                     else 
                        yyerror(
                           "import policy is greater than maximum allowed");
                     
                  }
                  free(ptr->value.string);
                  free(ptr);
               }
            }
            ;

policy_list : STRING {
               addstring2list($1);
            }
            | STRING policy_list {
               addstring2list($1);
            }
            ;
%%

struct keywords {
   const char *key_name;
   int key_val;
};

int
yyerror(const char *fmt, ...)
{
   va_list ap;
   char nfmt[BUFSIZ];

   file->errors++;
   va_start(ap, fmt);
   vsnprintf(nfmt, BUFSIZ, fmt, ap);
   va_end(ap);
   error("%s:%d %s", file->name, yylval.lineno, nfmt);
   return 0;
}

int
key_cmp(const void *key, const void *e_val)
{
   return strcmp(key, ((const struct keywords *)e_val)->key_name);
}

int
lookup(char *string)
{
   int retval = 0;
   static const struct keywords keywords[] = {
      { "accept", ACCEPT },
      { "aspath", ASPATH },
      { "autonomous-system", ASN },
      { "description", DESC },
      { "export", EXPORT },
      { "external",  EXTERNAL },
      { "from",  FROM },
      { "group",   GROUP },
      { "internal",  INTERNAL },
      { "local-as", LOCALAS },
      { "neighbor", NEIGHBOR },
      { "next", NEXT },
      { "policy-in", POLICYIN },
      { "policy-out", POLICYOUT },
      { "policy-statement", POLICY },
      { "prefix-list", PREFIXLIST },
      { "prepend", PREPEND },
      { "priority", PRIORITY },
      { "reject", REJECT },
      { "remote-as", REMOTEAS },
      { "route-filter", ROUTEFILTER },
      { "term", TERM },
      { "then", THEN },
      { "type",   TYPE },
   };

   const struct keywords *ptr = bsearch(string, keywords,
      sizeof(keywords)/sizeof(keywords[0]), sizeof(keywords[0]), key_cmp);

   if (ptr) 
		retval = ptr->key_val;
   else
      retval = (STRING);

	return retval;
}

#define MAXPUSHBACK	128

u_char	*parsebuf;
u_char	 pushback_buffer[MAXPUSHBACK];
int	 parseindex;
int	 pushback_index = 0;

int
lgetc(int quotec)
{
	int		c, next;

	if (parsebuf) {
		/* Read character from the parsebuffer instead of input. */
		if (parseindex >= 0) {
			c = parsebuf[parseindex++];
			if (c != '\0')
				return (c);
			parsebuf = NULL;
		} else
			parseindex++;
	}

	if (pushback_index)
		return (pushback_buffer[--pushback_index]);

	if (quotec) {
		if ((c = getc(file->stream)) == EOF) {
			yyerror("reached end of file while parsing "
			    "quoted string");
			if (file == topfile)
				return (EOF);
			return (quotec);
		}
		return (c);
	}

	while ((c = getc(file->stream)) == '\\') {
		next = getc(file->stream);
		if (next != '\n') {
			c = next;
			break;
		}
		yylval.lineno = file->lineno;
		file->lineno++;
	}

	while (c == EOF) {
		if (file == topfile)
			return (EOF);
		c = getc(file->stream);
	}
	return (c);
}

int
lungetc(int c)
{
	if (c == EOF)
		return (EOF);
	if (parsebuf) {
		parseindex--;
		if (parseindex >= 0)
			return (c);
	}
	if (pushback_index < MAXPUSHBACK-1)
		return (pushback_buffer[pushback_index++] = c);
	else
		return (EOF);
}

int
findeol(void)
{
	int	c;

	parsebuf = NULL;

	/* skip to either EOF or the first real EOL */
	while (1) {
		if (pushback_index)
			c = pushback_buffer[--pushback_index];
		else
			c = lgetc(0);
		if (c == '\n') {
			file->lineno++;
			break;
		}
		if (c == EOF)
			break;
	}
	return -1;
}

int
yylex(void)
{
#if __APPLE__ || __MACH__
   char buf[8096];
   char *ptr;
#else   
	u_char	 buf[8096];
	u_char	*ptr;
#endif
	int	 quotec, next, ch, token;

top:
	ptr = buf;
	while ((ch = lgetc(0)) == ' ' || ch == '\t')
		;

	yylval.lineno = file->lineno;
	if (ch == '#')
		while((ch = lgetc(0)) != '\n' && ch != EOF)
			;
	
	if (ch == '$' && parsebuf == NULL) {
		while (1) {
			if ((ch = lgetc(0)) == EOF)
				return 0;

			if (ptr + 1 >= buf + sizeof(buf) - 1) {
				yyerror("string too long");
				return findeol();
			}
			if (isalnum(ch) || ch == '_') {
				*ptr++ = ch;
				continue;
			}
			*ptr = '\0';
			lungetc(ch);
			break;
		}
		goto top;
	}

	switch (ch) {
	case '\'':
	case '"':
		quotec = ch;
		while (1) {
			if ((ch = lgetc(quotec)) == EOF)
				return (0);
			if (ch == '\n') {
				file->lineno++;
				continue;
			} else if (ch == '\\') {
				if ((next = lgetc(quotec)) == EOF)
					return (0);
				if (next == quotec || ch == ' ' || ch == '\t')
					ch = next;
				else if (next == '\n') {
					file->lineno++;
					continue;
				} else
					lungetc(next);
			} else if (ch == quotec) {
				*ptr = '\0';
				break;
			}
			if (ptr + 1 >= buf + sizeof(buf) - 1) {
				yyerror("string too long");
				return findeol();
			}
			*ptr++ = ch;
		}
		yylval.v.string = strdup(buf);
		if (yylval.v.string == NULL)
         yyerror("yylex: strdup");
		return (STRING);
	}

#define allowed_to_end_number(x) \
	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')

	if (ch == '-' || isdigit(ch)) {
		do {
			*ptr++ = ch;
			if ((unsigned)(ptr - buf) >= sizeof(buf)) {
				yyerror("string too long");
				return findeol();
			}
		} while ((ch = lgetc(0)) != EOF && isdigit(ch));
		lungetc(ch);
		if (ptr == buf + 1 && buf[0] == '-')
			goto nodigits;
		if (ch == EOF || allowed_to_end_number(ch)) {
			*ptr = '\0';
#if __OpenBSD__
			const char *errstr = NULL;
			yylval.v.number = strtonum(buf, LLONG_MIN, LLONG_MAX, &errstr);
			if (errstr) {
				yyerror("\"%s\" invalid number: %s", buf, errstr);
				return findeol();
			}
#else
         char *errptr;
         yylval.v.number = strtol(buf, &errptr, 10);
         if (buf[0] == '\0' || *errptr != '\0') {
            yyerror("\"%s\" invalid number %s", buf, errptr);
            return findeol();
         }
         if (errno == ERANGE && (
            yylval.v.number == LONG_MAX || yylval.v.number == LONG_MIN)) {
            yyerror("\"%s\" number out of range %s", buf, errptr);
            return findeol();
         }
#endif
			return (NUMBER);
		} else {
nodigits:
			while (ptr > buf + 1)
				lungetc(*--ptr);
			ch = *--ptr;
			if (ch == '-')
				return (ch);
		}
	}

#define allowed_in_string(x) \
	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
	x != '{' && x != '}' && x != '<' && x != '>' && \
	x != '!' && x != '=' && x != '/' && x != '#' && \
	x != ','))

	if (isalnum(ch) || ch == ':' || ch == '_' || ch == '*') {
		do {
			*ptr++ = ch;
			if ((unsigned)(ptr - buf) >= sizeof(buf)) {
				yyerror("string too long");
				return findeol();
			}
		} while ((ch = lgetc(0)) != EOF && (allowed_in_string(ch)));
		lungetc(ch);
		*ptr = '\0';
		if ((token = lookup(buf)) == STRING)
			if ((yylval.v.string = strdup(buf)) == NULL)
				error("yylex: strdup");

		return (token);
	}
	if (ch == '\n') {
		yylval.lineno = file->lineno;
		file->lineno++;
	}
	if (ch == EOF)
		return (0);
	return (ch);
}

struct file *
pushfile(const char *name, int secret)
{
	struct file *nfile;

	secret = secret;
	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
		warning("calloc");
		return NULL;
	}

	if ((nfile->name = strdup(name)) == NULL) {
		warning("malloc");
		free(nfile);
		return NULL;
	}		

	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
		warning("%s", nfile->name);
		free(nfile->name);
		free(nfile);
		return NULL;
	}

   nfile->errors = 0;
	nfile->lineno = 1;
	TAILQ_INSERT_TAIL(&files, nfile, entry);
	return nfile;
}

int
popfile(void)
{
	struct file *prev;
	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
		prev->errors += file->errors;

	TAILQ_REMOVE(&files, file, entry);
	fclose(file->stream);
	free(file->name);
	free(file);

	file = prev;

	return file ? 0 : EOF;
}

int
parse_config(char *filename)
{
   int errors = 0;
	if ((file = pushfile(filename, 1)) == NULL)
      error("%s:%s:%d, cannot push file to parse",
         __FILE__, __func__, __LINE__);

	topfile = file;
   TAILQ_INIT(&plist);
	yyparse();
   errors = file->errors;
   if (errors) {
      popfile();
      yyerror("there are errors in file");
   }
     
	popfile();

	return errors ? -1 : 0;
}

void
addnumber2list(u_int16_t number)
{
   struct list *ptr = (struct list *)calloc(1, sizeof(struct list));
   if (ptr == NULL)
      yyerror("calloc()");
   else {
      ptr->value.number = number;
      TAILQ_INSERT_HEAD(&plist, ptr, entry);
   }
}

void
addstring2list(const char *string)
{
   struct list *ptr = (struct list *)calloc(1, sizeof(struct list));
   if (ptr == NULL)
      yyerror("calloc()");
   else {
      ptr->value.string = strdup(string);
      TAILQ_INSERT_HEAD(&plist, ptr, entry);
   }
}   

int
plist_length()
{
   int count = 0;
   struct list *ptr;
   TAILQ_FOREACH(ptr, &plist, entry)
      count++;

   return count;
}
